/**
 * FleetManager.java
 *
 * Roslan's Remake of MOO in 3D using Ardor3D
 * (C) 2011 Roslan Amir. All rights reserved.
 */

package com.roslan.games.moo3d;

import java.awt.Point;
import java.util.ArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ardor3d.math.Vector2;
import com.ardor3d.math.Vector3;
import com.roslan.games.moo3d.data.Star;
import com.roslan.games.moo3d.events.ColonizeEvent;
import com.roslan.games.moo3d.events.ExploreEvent;
import com.roslan.games.moo3d.spatial.FleetNode;
import com.roslan.games.moo3d.spatial.FleetNode.FleetStatus;
import com.roslan.games.moo3d.spatial.StarNode;
import com.roslan.games.moo3d.utils.HexGridUtils;

/**
 * Comments go here.
 * 
 * @author Roslan Amir
 * @version 1.0 - Feb 5, 2011
 */
public class FleetManager {

	/**
     * 
     */
	private static final Logger logger = LoggerFactory.getLogger(Base.class);

	/**
	 * The singleton instance of this class.
	 */
	private static FleetManager instance;

	/**
	 * Reference to the single instance of the GameData class.
	 */
	private final GameData gameData = GameData.getInstance();

	/**
	 * Holds the tile X coordinate for the fleet being moved.
	 */
	private transient int targetTileX;

	/**
	 * Holds the tile Y coordinate for the fleet being moved.
	 */
	private transient int targetTileY;

	/**
	 * Default no-arg constructor.
	 */
	private FleetManager() {
	}

	/**
	 * Returns the singleton instance of this class.
	 * 
	 * @return the singleton instance of this class
	 */
	public static FleetManager getInstance() {
		if (instance == null) {
			instance = new FleetManager();
		}
		return instance;
	}

	/**
	 * Add the specified fleet to the orbiting list around the specified star.
	 * 
	 * @param starNode
	 * @param fleetNode
	 */
	public void addFleetToOrbitingList(StarNode starNode, FleetNode fleetNode) {
		ArrayList<FleetNode> orbitList = GameData.getOrbitingFleets(gameData.currentFleetNode.orbiting);
		orbitList.add(gameData.currentFleetNode);

		/*
		 * Update the position (translation) of the fleet.
		 */
		final Vector3 position = Vector3.fetchTempInstance();
		position.set(starNode.getTranslation());
		position.addLocal(FleetNode.HORIZONTAL_GAP, 0, FleetNode.HORIZONTAL_GAP + FleetNode.VERTICAL_GAP * (3 * orbitList.size() - 2));
		fleetNode.setTranslation(position);
	}

	/**
	 * Remove the specified fleet from the orbiting list around the specified star.
	 * 
	 * @param starNode
	 * @param fleetNode
	 */
	public void removeFleetFromOrbitingList(StarNode starNode, FleetNode fleetNode) {
		ArrayList<FleetNode> list = GameData.getOrbitingFleets(starNode);
		list.remove(fleetNode);

		/*
		 * Update the position (translation) of the remaining fleets in orbit (if any).
		 */
		if (list.size() > 0) {
			final Vector3 position = Vector3.fetchTempInstance();
			for (int i = 0; i < list.size(); i++) {
				position.set(starNode.getTranslation());
				position.addLocal(FleetNode.HORIZONTAL_GAP, 0, FleetNode.HORIZONTAL_GAP + FleetNode.VERTICAL_GAP * (3 * i + 1));
				fleetNode.setTranslation(position);
			}
			Vector3.releaseTempInstance(position);
		}
	}

	/**
	 * Add the specified fleet to the departing list around the specified star.
	 * 
	 * @param starNode
	 * @param fleetNode
	 */
	public void addFleetToDepartingList(StarNode starNode, FleetNode fleetNode) {
		ArrayList<FleetNode> list = GameData.getDepartingFleets(gameData.currentFleetNode.orbiting);
		list.add(gameData.currentFleetNode);

		/*
		 * Update the position (translation) of the fleet.
		 */
		final Vector3 position = Vector3.fetchTempInstance();
		position.set(starNode.getTranslation());
		position.addLocal(0, 0, FleetNode.HORIZONTAL_GAP + FleetNode.VERTICAL_GAP * (3 * list.size() - 2));
		fleetNode.setTranslation(position);
	}

	/**
	 * Remove the specified fleet from the departing list around the specified star.
	 * 
	 * @param fleetNode
	 */
	public void removeFleetFromDepartingList(final FleetNode fleetNode) {
		final StarNode starNode = fleetNode.orbiting;

		/*
		 * Remove this fleet from the departing list.
		 */
		final ArrayList<FleetNode> list = GameData.getDepartingFleets(starNode);
		list.remove(fleetNode);

		/*
		 * Update the position (translation) of the remaining fleets (if any).
		 */
		if (!list.isEmpty()) {
			final Vector3 position = Vector3.fetchTempInstance();
			for (int i = 0; i < list.size(); i++) {
				position.set(starNode.getTranslation());
				position.addLocal(0, 0, FleetNode.HORIZONTAL_GAP + FleetNode.VERTICAL_GAP * (3 * i + 1));
				fleetNode.setTranslation(position);
			}
			Vector3.releaseTempInstance(position);
		}
	}

	/**
	 * Put the specified fleet into orbit around this star. If there is already a fleet for this race at this star, merge them.
	 * 
	 * @param starNode - the star where to orbit the fleet
	 * @param newFleetNode - the fleet to put into orbit around this star
	 * @return the same or merged fleet
	 */
	public FleetNode putFleetInOrbit(final StarNode starNode, final FleetNode newFleetNode) {
		/*
		 * Check if another fleet of this race is already in orbit. If so, merge them.
		 */
		final ArrayList<FleetNode> list = GameData.getOrbitingFleets(starNode);
		if (!list.isEmpty()) {
			for (FleetNode currFleetNode : list) {
				if (currFleetNode.getFleet().owner == newFleetNode.getFleet().owner) {
					newFleetNode.removeFromParent(); // remove from the scene graph
					currFleetNode.merge(newFleetNode);
					GameData.discard(newFleetNode);
					return currFleetNode;
				}
			}
		}

		/*
		 * Only fleet of this race here. Add fleet to the orbiting list.
		 */
		newFleetNode.tile.x = starNode.tile.x;
		newFleetNode.tile.y = starNode.tile.y;
		newFleetNode.status = FleetStatus.ORBITING;
		newFleetNode.orbiting = starNode;
		newFleetNode.destination = null;
		newFleetNode.distanceToDestination = 0;
		newFleetNode.turnsToArrive = 0;

		/*
		 * Add the specified fleet to the orbiting list.
		 */
		addFleetToOrbitingList(starNode, newFleetNode);

		return newFleetNode;
	}

	/**
	 * Prepare to move the given fleet in the next frame cycle.
	 * 
	 * @param fleetNode - the fleet to move
	 */
	public void prepareToMoveFleet(FleetNode fleetNode) {
		logger.debug("Inside prepareToMoveFleet...");

		final StarNode destination = fleetNode.destination;

		/*
		 * Just departing from orbit. Set up the queue of tiles along the path.
		 */
		if (fleetNode.status == FleetStatus.DEPARTING) {
			logger.debug("DEPARTING...");
			Point[] path = HexGridUtils.path(fleetNode.tile, destination.tile);
			for (int i = 0; i < path.length; i++) {
				Vector2 point = Vector2.fetchTempInstance();
				point.set(path[i].x, path[i].y);
				fleetNode.pathTilesList.add(point);
			}
//			fleetNode.tile.x = 0; -- no change
//			fleetNode.tile.y = 0; -- no change
			fleetNode.status = FleetStatus.ENROUTE;
			fleetNode.orbiting = null;
//			fleetNode.destination = null; -- no change
//			fleetNode.distanceToDestination = 0; -- no change
//			fleetNode.turnsToArrive = 0; -- no change
		}

		/*
		 * Starting location of the fleet.
		 */
		Vector3 startLocation = (Vector3) fleetNode.getTranslation();
		logger.debug("Starting location = " + startLocation);
		logger.debug("Starting tile = (" + fleetNode.tile.x + ',' + fleetNode.tile.y + ')');
		double endX = fleetNode.tile.x;
		double endY = fleetNode.tile.y;

		/*
		 * Loop to cater for fleet speed of more than one.
		 */
		int tileCount = fleetNode.getFleet().warpSpeed;
		while (tileCount > 0) {
			/*
			 * Get next tile from list.
			 */
			Vector2 target = fleetNode.pathTilesList.remove();
			endX = target.getX();
			endY = target.getY();
			double[] endTuple = HexGridUtils.getHexWorldOrigin(endX, endY, null);
			Vector2.releaseTempInstance(target);

			/*
			 * Ending location of the fleet (for this turn).
			 */
			Vector3 endLocation = Vector3.fetchTempInstance();
			endLocation.set(endTuple[0], endTuple[1], 0);

			/*
			 * Extrapolate intermediate vectors along the path from start tile to end tile.
			 */
			for (int i = 1; i < 10; i++) {
				Vector3 vector = Vector3.fetchTempInstance();
				startLocation.lerp(endLocation, 0.1 * i, vector);
				fleetNode.pathVectorsList.add(vector);
			}
			fleetNode.pathVectorsList.add(endLocation);

			if (endX == destination.tile.x && endY == destination.tile.y) {
				break;
			}
			tileCount--;
			startLocation = endLocation;
		}
		logger.debug("Ending location = " + startLocation);

		targetTileX = (int) endX;
		targetTileY = (int) endY;
		logger.debug("Ending tile = (" + targetTileX + ',' + targetTileY + ')');

		logger.debug("Leaving prepareToMoveFleet...");
	}

	/**
	 * Move the fleet to simulate move animation to the target tile.
	 * 
	 * @param fleetNode - the fleet to move
	 * @return <code>true</code> if no more moves left, <code>false</code> otherwise
	 */
	public boolean moveFleet(FleetNode fleetNode) {
		logger.debug("Inside moveFleet...");

		/*
		 * More moves this turn.
		 */
		if (!fleetNode.pathVectorsList.isEmpty()) {
			Vector3 vector = fleetNode.pathVectorsList.remove();
			fleetNode.setTranslation(vector);
			Vector3.releaseTempInstance(vector);
			logger.debug("Leaving moveFleet...Moving one step");
			return false;
		}

		/*
		 * No more moves this turn but still have not arrived at destination.
		 */
		if (!fleetNode.pathTilesList.isEmpty()) {
			fleetNode.tile.x = targetTileX;
			fleetNode.tile.y = targetTileY;
//			fleetNode.status = FleetStatus.ENROUTE; -- no change
//			fleetNode.orbiting = null; -- no change
//			fleetNode.destination = null; -- no change
			fleetNode.distanceToDestination -= fleetNode.getFleet().warpSpeed;
			fleetNode.turnsToArrive--;
			logger.debug("Leaving moveFleet...No more moves this turn");
			return true;
		}

		/*
		 * Arrive at destination star.
		 */
		final StarNode starNode = fleetNode.destination;
		final Star star = starNode.getStar();

		/*
		 * Arrive at an already explored star.
		 */
		if (star.explored) {
			/*
			 * If star is not colonized and if the fleet contains a suitable colony ship, offer to colonize the system.
			 */
			if (star.owner == null && fleetNode.canColonize(star)) {
				gameData.eventQueue.add(new ColonizeEvent(starNode, fleetNode));
			}
			/*
			 * Otherwise, just put the fleet into orbit.
			 */
			else {
				putFleetInOrbit(starNode, fleetNode);
			}
		}

		/*
		 * Arrive at an unexplored star.
		 */
		else {
			/*
			 * If a ship in the fleet has a suitable colony ship, offer to colonize the system.
			 */
			if (fleetNode.canColonize(star)) {
				gameData.eventQueue.add(new ColonizeEvent(starNode, fleetNode));
			}
			/*
			 * Otherwise, just show the exploration info panel and put the fleet into orbit.
			 */
			else {
				gameData.eventQueue.add(new ExploreEvent(starNode, fleetNode));
			}
		}

		logger.debug("Leaving moveFleet...Arrived");

		return true;
	}

}
